﻿using System;
using System.IO;
using System.Runtime.CompilerServices;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.Audio
{
    unsafe public partial class WaveHelper
    {
        private const int StepMin = 16;
        private const int StepMax = 127;
        private const int StepSize = 8;
        private const int Delta = 1;


        #region CVSD转pcm

        /// <summary>
        /// CVSD数据文件转换为pcm数据文件
        /// </summary>
        /// <param name="csvdFilePath">带头的CVSD数据文件路径</param>
        /// <param name="pcmFilePath">pcm数据文件存储路径</param>
        public static void CsvdToPcm(string csvdFilePath, string pcmFilePath)
        {
            if (string.IsNullOrEmpty(csvdFilePath))
            {
                throw new ArgumentNullException(nameof(csvdFilePath));
            }

            if (!File.Exists(csvdFilePath))
            {
                throw new FileNotFoundException("带头的CVSD数据文件路径", csvdFilePath);
            }

            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(csvdFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.IbmCvsd);
                var srcDataPtr = new UnmanagedMemoryPointer<byte>(srcFileStream.Length - srcFileWaveHeadSize);
                disposabler.Add(srcDataPtr, 0);

                var waveInfo = GetWavHead(srcFileStream);
                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.GetSpan());

                var destDataPtr = new UnmanagedMemoryPointer<short>(srcDataPtr.Length * 8);
                disposabler.Add(destDataPtr, 0);
                PrimitiveCsvdToPcm(srcDataPtr.DataPtr, destDataPtr.DataPtr, srcDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(pcmFilePath);
                var destFileStream = new FileStream(pcmFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.Pcm);
                destFileStream.Write(destDataPtr.Cast<byte>().ToSpan());

                FillPcmWavHead(destFileStream, WaveEncoding.Pcm, DataSampleBits.BIT16, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)(destDataPtr.Length * sizeof(short)));
            }
        }

        /// <summary>
        /// CVSD数据转换为pcm数据
        /// </summary>
        /// <param name="csvdData">CVSD数据</param>
        /// <returns>pcm数据</returns>
        public static short[] CsvdToPcm(byte[] csvdData)
        {
            if (csvdData == null)
            {
                throw new ArgumentNullException(nameof(csvdData));
            }

            short[] pcmData = new short[csvdData.Length * 8];

            fixed (byte* csvdDataPtr = csvdData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveCsvdToPcm(csvdDataPtr, pcmDataPtr, csvdData.Length);
                }
            }

            return pcmData;
        }

        /// <summary>
        /// CVSD数据转换为pcm数据
        /// </summary>
        /// <param name="csvdData">CVSD数据</param>
        /// <param name="csvdDataIndex">CVSD数据起始索引</param>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="length">转换CVSD数据长度</param>
        public static void CsvdToPcm(byte[] csvdData, int csvdDataIndex, short[] pcmData, int pcmDataIndex, int length)
        {
            if (csvdData == null)
            {
                throw new ArgumentNullException(nameof(csvdData));
            }

            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (csvdDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(csvdDataIndex), "CVSD数据起始索引不能小于0");
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (csvdDataIndex + length > csvdData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"CVSD数据起始索引\"加\"转换数据长度\"大于了\"CVSD数据\"长度");
            }

            if (pcmDataIndex + length * 8 > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (byte* csvdDataPtr = csvdData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveCsvdToPcm(csvdDataPtr + csvdDataIndex, pcmDataPtr + pcmDataIndex, length);
                }
            }

        }

        /// <summary>
        /// CVSD数据转换为pcm数据
        /// </summary>
        /// <param name="csvdData">CVSD数据</param>
        /// <param name="pcmData">pcm数据</param>
        public static void CsvdToPcm(Span<byte> csvdData, Span<short> pcmData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            byte* csvdDataPtr = csvdData.DataPtr;
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            byte* csvdDataPtr = (byte*)Unsafe.AsPointer<byte>(ref csvdData[0]);
#endif
            PrimitiveCsvdToPcm(csvdDataPtr, pcmDataPtr, Math.Min(csvdData.Length, pcmData.Length / 8));
        }

        /// <summary>
        /// CVSD数据转换为pcm数据
        /// </summary>
        /// <param name="csvdData">CVSD数据</param>
        /// <param name="pcmData">pcm数据</param>
        public static void CsvdToPcm(SpanZ<byte> csvdData, SpanZ<short> pcmData)
        {
            PrimitiveCsvdToPcm(csvdData.DataPtr, pcmData.DataPtr, Math.Min(csvdData.Length, pcmData.Length / 8));
        }



        private static void PrimitiveCsvdToPcm(byte* csvdDataPtr, short* pcmDataPtr, int csvdDataLength)
        {
            int step = StepMin;
            int integrator = 0;
            int pcmIndex = 0;
            byte cvsdByte;

            for (int j = 0; j < csvdDataLength; j++)
            {
                cvsdByte = csvdDataPtr[j];

                for (int i = 7; i >= 0; i--)
                {
                    byte bit = (byte)((cvsdByte >> i) & 1);

                    if (bit == 1)
                    {
                        integrator += step;
                    }
                    else
                    {
                        integrator -= step;
                    }

                    pcmDataPtr[pcmIndex++] = (short)integrator;

                    if (bit == 1)
                    {
                        step = Math.Min(step + StepSize, StepMax);
                    }
                    else
                    {
                        step = Math.Max(step - StepSize, StepMin);
                    }
                }
            }
        }


        #endregion



        #region pcm转CVSD

        /// <summary>
        /// pcm数据文件转换为csvd数据文件
        /// </summary>
        /// <param name="pcmFilePath">带头的pcm数据文件路径</param>
        /// <param name="csvdFilePath">csvd数据文件存储路径</param>
        public static void PcmToCSVD(string pcmFilePath, string csvdFilePath)
        {
            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            if (!File.Exists(pcmFilePath))
            {
                throw new FileNotFoundException("带头的pcm数据文件路径不存在", pcmFilePath);
            }

            if (string.IsNullOrEmpty(csvdFilePath))
            {
                throw new ArgumentNullException(nameof(csvdFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(pcmFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                var waveInfo = GetWavHead(srcFileStream);
                if (waveInfo.SampleBits != 16)
                {
                    throw new InvalidOperationException($"仅支持16位采样位数的pcm数据文件转换为CVSD数据文件,当前数据采样位数为{waveInfo.SampleBits}");
                }

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.Pcm);
                var srcDataPtr = new UnmanagedMemoryPointer<short>((srcFileStream.Length - srcFileWaveHeadSize) / sizeof(short));
                disposabler.Add(srcDataPtr, 0);

                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.Cast<byte>().ToSpan());

                var destDataPtr = new UnmanagedMemoryPointer<byte>((srcDataPtr.Length + 7) / 8);
                disposabler.Add(destDataPtr, 0);
                PrimitivePcmToCSVD(srcDataPtr.DataPtr, destDataPtr.DataPtr, srcDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(csvdFilePath);
                var destFileStream = new FileStream(csvdFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.IbmCvsd);
                destFileStream.Write(destDataPtr.GetSpan());

                FillCSVDWavHead(destFileStream, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destDataPtr.Length);
            }
        }

        /// <summary>
        /// pcm数据转换为csvd数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <returns>csvd数据</returns>
        public static byte[] PcmToCSVD(short[] pcmData)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            byte[] csvdData = new byte[(pcmData.Length + 7) / 8];
            fixed (byte* csvdDataPtr = csvdData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToCSVD(pcmDataPtr, csvdDataPtr, pcmData.Length);
                }
            }
            return csvdData;
        }

        /// <summary>
        /// pcm数据转换为csvd数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="csvdData">csvd数据</param>
        /// <param name="csvdDataIndex">csvd数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void PcmToCSVD(short[] pcmData, int pcmDataIndex, byte[] csvdData, int csvdDataIndex, int length)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (csvdData == null)
            {
                throw new ArgumentNullException(nameof(csvdData));
            }

            if (csvdDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(csvdDataIndex), "CVSD数据起始索引不能小于0");
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (csvdDataIndex + (length + 7) / 8 > csvdData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"CVSD数据起始索引\"加\"转换数据长度\"大于了\"CVSD数据\"长度");
            }

            if (pcmDataIndex + length > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (byte* csvdDataPtr = csvdData)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToCSVD(pcmDataPtr + pcmDataIndex, csvdDataPtr + csvdDataIndex, length);
                }
            }
        }

        /// <summary>
        /// pcm数据转换为csvd数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="csvdData">csvd数据</param>
        public static void PcmToCSVD(Span<short> pcmData, Span<byte> csvdData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            byte* csvdDataPtr = csvdData.DataPtr;
            
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            byte* csvdDataPtr = (byte*)Unsafe.AsPointer<byte>(ref csvdData[0]);
#endif

            PrimitivePcmToCSVD(pcmDataPtr, csvdDataPtr, Math.Min((pcmData.Length + 7) / 8, csvdData.Length));
        }

        /// <summary>
        /// pcm数据转换为csvd数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="csvdData">csvd数据</param>
        public static void PcmToCSVD(SpanZ<short> pcmData, SpanZ<byte> csvdData)
        {
            PrimitivePcmToCSVD(pcmData.DataPtr, csvdData.DataPtr, Math.Min((pcmData.Length + 7) / 8, csvdData.Length));
        }

        private static void PrimitivePcmToCSVD(short* pcmDataPtr, byte* csvdDataPtr, int pcmDataLength)
        {
            int bitCount = 0;
            byte currentByte = 0;
            int step = StepMin;
            int integrator = 0, prediction, cvsdIndex = 0, error;
            short pcmSample;

            for (int i = 0; i < pcmDataLength; i++)
            {
                pcmSample = pcmDataPtr[i];
                prediction = integrator;
                error = pcmSample - prediction;

                byte bit;
                if (error >= 0)
                {
                    bit = 1;
                    integrator += step;
                }
                else
                {
                    bit = 0;
                    integrator -= step;
                }

                currentByte |= (byte)(bit << (7 - bitCount));
                bitCount++;

                if (bitCount == 8)
                {
                    csvdDataPtr[cvsdIndex++] = currentByte;
                    bitCount = 0;
                    currentByte = 0;
                }

                if (bit == 1)
                {
                    step = Math.Min(step + StepSize, StepMax);
                }
                else
                {
                    step = Math.Max(step - StepSize, StepMin);
                }
            }

            if (bitCount > 0)
            {
                csvdDataPtr[cvsdIndex] = currentByte;
            }
        }

        #endregion


    }
}
